/**
 * 客户端项目版本模型
 *
 * Created by snowingsea on 2020/02/13.
 */
const fs = require('fs-extra');
const path = require('path');
const querystring = require('querystring');
const request = require('request-promise');
const randomstring = require('randomstring');
const FunctionModel = require('./function');
const TestCase = require('./test-case');
const testerURI = require('./../api/tester/uri');
const compilationOptions = require('./../benchmark/project-version/compilation_options');

const files = require('./../../phoenix/models/files');
const { coverageKind } = require('./../../phoenix/models/tests');

class ProjectVersion {
  constructor(project, versionId, versionName, status, rootDirectory = undefined, oldVersionId = undefined) {
    this.project = project;
    this.versionId = versionId;
    this.versionName = versionName;
    this.status = status;
    this.rootDirectory = rootDirectory;
    this.oldVersionId = oldVersionId;
  }

  /**
   * 更新版本文件
   * @param filename 压缩包名
   * @return {Promise}
   */
  updateVersionFiles(filename, config = {}) {
    return new Promise((resolve, reject) => {
      this.project.creator.login()
        .then(() => {
          const type = files.repositoryType.file;
          const options = {
            url: `${testerURI}/files/${this.rootDirectory.fileId}/file.json?${querystring.stringify({ type })}`,
            headers: { Authorization: `Bearer ${this.project.creator.token.accessToken}` },
            formData: {
              file: fs.createReadStream(path.join(process.cwd(), 'test', 'benchmark', 'source-files', filename)),
            },
          };
          return request.put(options);
        })
        .then(() => {
          const options = {
            url: `${testerURI}/project-versions/${this.versionId}.json`,
            headers: { Authorization: `Bearer ${this.project.creator.token.accessToken}` },
            body: config,
            json: true,
          };
          return request.put(options);
        })
        .then(() => this.project.creator.logout())
        .then(resolve)
        .catch(reject);
    });
  }

  /**
   * 通过git更新源码
   * @param {String} url git地址
   * @param {String} branch 分支名
   * @param {String} username 用户名
   * @param {String} password 密码
   * @param {Object} config 版本配置信息
   * @return {Promise}
   */
  updateSourceByGit(url, { branch = 'master', username = undefined, password = undefined } = {}, config = {}) {
    return new Promise((resolve, reject) => {
      this.project.creator.login()
        .then(() => {
          const query = {
            type: files.repositoryType.git,
            url,
            branch,
            username,
          };
          const options = {
            url: `${testerURI}/files/${this.rootDirectory.fileId}/file.json?${querystring.stringify(query)}`,
            form: { password },
            headers: { Authorization: `Bearer ${this.project.creator.token.accessToken}` },
          };
          return request.put(options);
        })
        .then(() => {
          const options = {
            url: `${testerURI}/project-versions/${this.versionId}.json`,
            headers: { Authorization: `Bearer ${this.project.creator.token.accessToken}` },
            body: config,
            json: true,
          };
          return request.put(options);
        })
        .then(() => this.project.creator.logout())
        .then(resolve)
        .catch(reject);
    });
  }

  /**
   * 获取所有函数列表
   * @param {Object} benchmark 被测代码信息
   * @param {Object} config 版本配置信息
   * @return {Promise}
   */
  getAllFunctions(benchmark) {
    const { config, files, git } = benchmark;
    let extensions = config.headers.suffixes;
    if (config.languages.c) {
      extensions = config.languages.c.suffixes.concat(extensions);
    }
    if (config.languages['c++']) {
      extensions = config.languages['c++'].suffixes.concat(extensions);
    }
    return Promise.resolve()
      .then(() => {
        if (git) {
          return this.updateSourceByGit(git.url, git, config);
        }
        if (files) {
          return this.updateVersionFiles(files, config);
        }
        return Promise.resolve();
      })
      .then(() => this.rootDirectory.analyse())
      .then(() => this.rootDirectory.getTree(extensions),
        () => this.rootDirectory.getTree(extensions))
      .then((tree) => {
        const allFiles = [];
        const rootArray = [tree.tree];
        while (rootArray.length > 0) {
          const root = rootArray.pop();
          if (root.children) {
            root.children.forEach((child) => {
              rootArray.push(child);
            });
          }
          allFiles.push(root);
        }
        const promises = allFiles.map((file) => {
          const options = {
            url: `${testerURI}/files/${file.fileId}/functions.json`,
            headers: { Authorization: `Bearer ${this.project.creator.token.accessToken}` },
            json: true,
          };
          return request.get(options);
        });
        return Promise.all(promises);
      })
      .then((results) => {
        const allFunctions = [];
        results.forEach((result) => {
          result.functions.forEach(({ fileId, functionId, name }) => {
            const func = new FunctionModel(this.rootDirectory, fileId, functionId, name);
            allFunctions.push(func);
          });
        });
        return allFunctions;
      });
  }

  /**
   * 构造简单的函数类型
   * @param {String} functionId 函数Id
   * @param {String} fileId 文件Id
   * @param {String} functionName 函数名
   * @return {Promise}
   */
  generateSimpleFunctionModel(functionId, fileId, functionName) {
    const func = new FunctionModel(this.rootDirectory, fileId, functionId, functionName);
    return Promise.resolve(func);
  }

  /**
   * 获取所有测试用例列表
   * @param {Object} benchmark 被测文件信息
   * @return {Promise}
   */
  getAllTests(benchmark) {
    return new Promise((resolve, reject) => {
      let allFunctions;
      const allTests = [];
      this.getAllFunctions(benchmark)
        .then((res) => {
          allFunctions = res;
          return this.rootDirectory.generateTests();
        })
        .then(() => this.project.creator.login())
        .then(() => {
          const promises = allFunctions.map((func) => {
            const { functionId } = func;
            const options = {
              url: `${testerURI}/functions/${functionId}/tests.json`,
              headers: { Authorization: `Bearer ${this.project.creator.token.accessToken}` },
              json: true,
            };
            return request.get(options);
          });
          return Promise.all(promises);
        })
        .then((results) => {
          allFunctions.forEach((func, index) => {
            results[index].tests.forEach((test) => {
              const {
                testId,
                status,
                kind,
                data,
              } = test;
              allTests.push(new TestCase(func, func.functionId, testId, status, kind, data));
            });
          });
          return this.project.creator.logout();
        })
        .then(() => {
          resolve(allTests);
        })
        .catch(reject);
    });
  }

  /**
   * 随机获取单个函数下的测试用例
   * @param {Object} benchmark 被测文件信息
   * @return {Promise<unknown>}
   */
  getFunctionTests(benchmark) {
    return new Promise((resolve, reject) => {
      let allFunctions;
      const allTests = [];
      this.getAllFunctions(benchmark)
        .then((res) => {
          allFunctions = res;
          return this.rootDirectory.generateTests();
        })
        .then(() => this.project.creator.login())
        .then(() => {
          const { functionId } = allFunctions[0];
          const options = {
            url: `${testerURI}/functions/${functionId}/tests.json`,
            headers: { Authorization: `Bearer ${this.project.creator.token.accessToken}` },
            json: true,
          };
          return request.get(options);
        })
        .then((results) => {
          results.tests.forEach((test) => {
            const {
              testId,
              status,
              kind,
              data,
            } = test;
            const { functionId } = allFunctions[0];
            allTests.push(new TestCase(allFunctions[0], functionId, testId, status, kind, data));
          });
          return this.project.creator.logout();
        })
        .then(() => {
          resolve(allTests);
        })
        .catch(reject);
    });
  }

  /**
   * 获取指定函数下的测试用例
   * @param functionId
   * @return {Promise<unknown>}
   */
  getTests(functionId) {
    return new Promise((resolve, reject) => {
      Promise.resolve()
        .then(() => {
          const options = {
            url: `${testerURI}/functions/${functionId}/tests.json`,
            headers: { Authorization: `Bearer ${this.project.creator.token.accessToken}` },
            json: true,
          };
          return request.get(options);
        })
        .then((results) => {
          resolve(results.tests);
        })
        .catch(reject);
    });
  }

  /**
   * 执行所有测试用例
   * @param {Object} benchmark 被测文件信息
   * @return {Promise}
   */
  runAllTests(benchmark) {
    return new Promise((resolve, reject) => {
      let allTests;
      this.getAllTests(benchmark)
        .then((res) => {
          allTests = res;
          return this.project.creator.login();
        })
        .then(() => {
          const options = {
            url: `${testerURI}/project-versions/${this.versionId}/results.json`,
            headers: { Authorization: `Bearer ${this.project.creator.token.accessToken}` },
            json: true,
          };
          return request.put(options);
        })
        .then(() => this.project.creator.waiting4TasksOfVersionFinished(this.versionId))
        .then(() => this.project.creator.logout())
        .then(() => {
          resolve(allTests);
        })
        .catch(reject);
    });
  }

  /**
   * 获取函数变量
   * @param {Object} benchmark 被测文件信息
   * @return {Promise}
   */
  getAllVariables(benchmark) {
    return new Promise((resolve, reject) => {
      const variables = [];
      let allFunctions;
      this.getAllFunctions(benchmark)
        .then((res) => {
          allFunctions = res;
          return this.rootDirectory.generateTests();
        })
        .then(() => {
          const promises = allFunctions.map((func) => {
            const { accessToken } = this.rootDirectory.projectVersion.project.creator.token;
            return new Promise((resolve, reject) => {
              func.getVariables(accessToken)
                .then((res) => {
                  variables.push(res);
                  resolve();
                })
                .catch(reject);
            });
          });
          return Promise.all(promises);
        })
        .then(() => {
          resolve(variables);
        })
        .catch(reject);
    });
  }

  /**
   * 获取所有桩函数信息
   * @param {Object} benchmark 被测文件信息
   * @return {Promise}
   */
  getAllStubs(benchmark) {
    return new Promise((resolve, reject) => {
      let response;
      this.getAllFunctions(benchmark)
        .then((allFunctions) => {
          const promises = [];
          allFunctions.forEach((func) => {
            const prom = new Promise((resolve, reject) => {
              Promise.resolve()
                .then(() => request.get({
                  url: `${testerURI}/functions/${func.functionId}/variables.json`,
                  headers: { Authorization: `Bearer ${this.project.creator.token.accessToken}` },
                  json: true,
                }))
                .then((res) => {
                  response = res;
                  resolve(response);
                })
                .catch(reject);
            });
            promises.push(prom);
          });
          return Promise.all(promises);
        })

        .then((responses) => {
          const stubs = [];
          responses.forEach((res) => {
            if (res && res.stubs) {
              Object.keys(res.stubs).forEach((name) => {
                stubs.push({
                  functionId: res.functionId,
                  stubName: name,
                  pointerTargets: res.stubs[name].pointerTargets,
                });
                resolve(stubs);
              });
            }
          });
          resolve(stubs);
        })
        .catch(reject);
    });
  }

  /**
   * 获取全局变量
   * @param {Object} benchmark 测试数据信息
   * @return {Promise<unknown>}
   */
  getAllGlobal(benchmark) {
    return new Promise((resolve, reject) => {
      this.getAllVariables(benchmark)
        .then((variables) => {
          const globals = [];
          variables.forEach((res) => {
            if (res.variables.global) {
              globals.push({ global: res.variables.global, functionId: res.functionId });
            }
          });
          resolve(globals);
        })
        .catch(reject);
    });
  }

  /**
   * 获取指针目标
   * @param {Object} benchmark 测试数据信息
   * @return {Promise<unknown>}
   */
  getAllMalloc(benchmark) {
    return new Promise((resolve, reject) => {
      this.getAllVariables(benchmark)
        .then((variables) => {
          const malloc = [];
          variables.forEach((res) => {
            if (res.malloc) {
              Object.keys(res.malloc).forEach((value) => {
                malloc.push({ functionId: res.functionId, malloc: value });
              });
            }
          });
          resolve(malloc);
        })
        .catch(reject);
    });
  }

  /**
   * 导出测试用例
   * @return {Promise}
   */
  exportTests() {
    let response;
    return new Promise((resolve, reject) => {
      this.project.creator.login()
        .then(() => request.get({
          url: `${testerURI}/project-versions/${this.versionId}/tests.json`,
          headers: { Authorization: `Bearer ${this.project.creator.token.accessToken}` },
          json: true,
        }))
        .then((res) => {
          response = res;
          this.project.creator.logout();
        })
        .then(() => {
          resolve(response);
        })
        .catch(reject);
    });
  }

  /**
   * 关联静态分析模板
   * @return {Promise}
   */
  linkAnalyzeTemplate(analyzeTemplateId) {
    let response;
    return new Promise((resolve, reject) => {
      const body = { staticAnalyze: { analyzeTemplate: analyzeTemplateId } };
      this.project.creator.login()
        .then(() => request.put({
          url: `${testerURI}/project-versions/${this.versionId}.json`,
          headers: { Authorization: `Bearer ${this.project.creator.token.accessToken}` },
          body,
          json: true,
        }))
        .then((res) => {
          response = res;
          this.project.creator.logout();
        })
        .then(() => {
          resolve(response);
        })
        .catch(reject);
    });
  }

  initSubVersion(masterVersionId, versionType) {
    let response;
    return new Promise((resolve, reject) => {
      this.project.creator.login()
        .then(() => request.post({
          url: `${testerURI}/project-versions/${masterVersionId}/init.json?${querystring.stringify({ versionType })}`,
          headers: { Authorization: `Bearer ${this.project.creator.token.accessToken}` },
          json: true,
        }))
        .then((res) => {
          response = res;
          this.project.creator.logout();
        })
        .then(() => {
          resolve(response);
        })
        .catch(reject);
    });
  }

  /**
   * 随机获取版本信息
   * @param {String} language 项目类型
   * @return {Object}
   */
  static getRandomProjectVersionInfo(language = 'c') {
    // eslint-disable-next-line max-len
    // const filteredTypes = Object.keys(compilationOptions['project types']).filter((type) => !!compilationOptions['project types'][type].languages[language]);
    const index = Math.floor(Math.random() * 10);
    // let projectType = filteredTypes[index];
    // if (projectType === undefined) {
    //   [projectType] = Object.keys(filteredTypes);
    // }
    const projectType = 'gcc';
    const headers = { suffixes: compilationOptions.languages[language].headers };
    const languages = {};
    ['c', language].forEach((lang) => {
      languages[lang] = { suffixes: compilationOptions.languages[lang].suffixes, standard: compilationOptions['project types'][projectType].languages[lang].standards[0] };
    });
    const system = {};
    // let system = Object.keys(compilationOptions.systems)[index];
    // if (system === undefined) {
    //   [system] = Object.keys(compilationOptions.systems);
    // }
    let environment = Object.keys(compilationOptions.environments)[index];
    if (environment === undefined) {
      [environment] = Object.keys(compilationOptions.environments);
    }
    const includes = [''];
    for (let i = Math.floor(Math.random() * 10); i >= 0; i -= 1) {
      let include = '';
      for (let j = Math.floor(Math.random() * 4); j >= 0; j -= 1) {
        include = path.join(include, randomstring.generate(8));
      }
      includes.push(include);
    }
    const macros = [];
    for (let i = Math.floor(Math.random() * 7); i >= 0; i -= 1) {
      // macro key must start with  alphabetic
      const macro = { key: `${randomstring.generate({ length: 1, charset: 'alphabetic' })}${randomstring.generate(7)}` };
      if (Math.random() > 0.5) {
        macro.value = randomstring.generate(8);
      }
      macros.push(macro);
    }
    return {
      versionName: randomstring.generate(8),
      language,
      headers: JSON.stringify(headers),
      languages: JSON.stringify(languages),
      includes,
      projectType,
      system,
      environment,
      macros: JSON.stringify(macros),
      timeout: Math.floor(Math.random() * 30),
      extraExit: randomstring.generate(8),
      // eslint-disable-next-line no-bitwise
      coverages: Math.ceil(Math.random() * coverageKind.all) | 1,
      caseSensitive: Math.random() > 0.5,
      systemType: Math.floor(Math.random() * 2),
      strictOutputCheck: Math.random() > 0.5,
    };
  }

  /**
   * 运行部分测试用例
   * @param {Array} functionIdArray 函数id列表
   * @return {Promise}
   */
  runFunctionTests(functionIdArray) {
    return new Promise((resolve, reject) => {
      this.project.creator.login()
        .then(() => {
          const form = { functionIds: functionIdArray };
          const options = {
            url: `${testerURI}/project-versions/${this.versionId}/results.json`,
            headers: { Authorization: `Bearer ${this.project.creator.token.accessToken}` },
            form,
            json: true,
          };
          return request.put(options);
        })
        // 等待任务结束
        .then(() => this.project.creator.waiting4TasksOfVersionFinished(this.versionId))
        .then(() => this.project.creator.logout())
        .then(resolve)
        .catch(reject);
    });
  }
}

/**
 * Export model definition object.
 */
module.exports = ProjectVersion;
